
// <copyright file="FolderWatcher.cs" company="Subhadra Software Solutions">
//   
// </copyright>
// <summary>
//   The folder watcher.
// </summary>

namespace SubhadraSolutions.Sharp.Utils.IO
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Xml;

    using SubhadraSolutions.Sharp.Utils.Collections.Concurrent;
    using SubhadraSolutions.Sharp.Utils.Contracts;

    /// <summary>
    /// The folder watcher.
    /// </summary>
    public sealed class FolderWatcher : AbstractInitializableAndDisposable, IInitializable
    {
        #region Fields

        /// <summary>
        /// The dictionary.
        /// </summary>
        private readonly CompositeDictionary<string, string> _dictionary = new CompositeDictionary<string, string>(true);

        /// <summary>
        /// The watcher.
        /// </summary>
        private FileSystemWatcher _watcher;

        #endregion Fields

        #region Public Properties

        /// <summary>
        /// Gets or sets the extensions.
        /// </summary>
        public List<string> Extensions { get; set; }

        /// <summary>
        /// Gets or sets the folder path.
        /// </summary>
        public string FolderPath { get; set; }

        #endregion Public Properties

        #region Public Methods and Operators

        /// <summary>
        /// The export as xml document.
        /// </summary>
        /// <returns>
        /// The <see cref="XmlDocument"/>.
        /// </returns>
        public XmlDocument ExportAsXmlDocument()
        {
            return CompositeKeyValue<string, string>.ExportAsXmlDocument(this.GetHierarchy());
        }

        /// <summary>
        /// The get hierarchy.
        /// </summary>
        /// <returns>
        /// The <see cref="List"/>.
        /// </returns>
        public List<CompositeKeyValue<string, string>> GetHierarchy()
        {
            return this._dictionary.GetCompleteTree();
        }

        /// <summary>
        /// The get if exists.
        /// </summary>
        /// <param name="fileName">
        /// The file name.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public string GetIfExists(string fileName)
        {
            string relativePath = fileName.Substring(this.FolderPath.Length);
            string[] splits = this.split(relativePath);
            string value;
            if (this._dictionary.TryGetValue(out value, splits))
            {
                return value;
            }

            return null;
        }

        #endregion Public Methods and Operators

        #region Methods

        /// <summary>
        /// The dispose.
        /// </summary>
        /// <param name="disposing">
        /// The disposing.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            this._watcher.Dispose();
            this._dictionary.Dispose();
        }

        /// <summary>
        /// The initialize protected.
        /// </summary>
        protected override void InitializeProtected()
        {
            var di = new DirectoryInfo(this.FolderPath);
            if (!di.Exists)
            {
                di.Create();
            }

            FileInfo[] files = di.GetFiles("*.*", SearchOption.AllDirectories);
            foreach (FileInfo fileInfo in files)
            {
                if (this.Extensions.Contains(fileInfo.Extension, StringComparer.OrdinalIgnoreCase))
                {
                    this.addOrRemove(fileInfo.FullName, true);
                }
            }

            this._watcher = this.watchDirectory(this.FolderPath);
        }

        /// <summary>
        /// The add or remove.
        /// </summary>
        /// <param name="fileName">
        /// The file name.
        /// </param>
        /// <param name="isAdd">
        /// The is add.
        /// </param>
        private void addOrRemove(string fileName, bool isAdd)
        {
            string relativePath = fileName.Substring(this.FolderPath.Length);
            string[] splits = this.split(relativePath);
            if (isAdd)
            {
                this._dictionary.AddOrUpdate(relativePath, splits);
            }
            else
            {
                this._dictionary.RemoveIfExists(splits);
            }
        }

        /// <summary>
        /// The split.
        /// </summary>
        /// <param name="relativePath">
        /// The relative path.
        /// </param>
        /// <returns>
        /// The <see cref="string[]"/>.
        /// </returns>
        private string[] split(string relativePath)
        {
            return relativePath.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
        }

        /// <summary>
        /// The watch directory.
        /// </summary>
        /// <param name="directory">
        /// The directory.
        /// </param>
        /// <returns>
        /// The <see cref="FileSystemWatcher"/>.
        /// </returns>
        private FileSystemWatcher watchDirectory(string directory)
        {
            directory = directory.ToUpper();
            var watcher = new FileSystemWatcher(directory);
            watcher.Created += this.watcher_Created;
            watcher.Deleted += this.watcher_Deleted;
            watcher.Renamed += this.watcher_Renamed;
            watcher.IncludeSubdirectories = true;
            watcher.EnableRaisingEvents = true;
            return watcher;
        }

        /// <summary>
        /// The watcher_ created.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void watcher_Created(object sender, FileSystemEventArgs e)
        {
            this._watcher.EnableRaisingEvents = false;
            this.addOrRemove(e.FullPath, false);
            this._watcher.EnableRaisingEvents = true;
        }

        /// <summary>
        /// The watcher_ deleted.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void watcher_Deleted(object sender, FileSystemEventArgs e)
        {
            this._watcher.EnableRaisingEvents = false;
            this.addOrRemove(e.FullPath, false);
            this._watcher.EnableRaisingEvents = true;
        }

        /// <summary>
        /// The watcher_ renamed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void watcher_Renamed(object sender, RenamedEventArgs e)
        {
            this._watcher.EnableRaisingEvents = false;
            this.addOrRemove(e.OldFullPath, false);
            this.addOrRemove(e.FullPath, true);
            this._watcher.EnableRaisingEvents = true;
        }

        #endregion Methods
    }
}
